package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> entities = baseMapper.selectList(null);
        return entities.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .peek(categoryEntity -> categoryEntity.setChildren(getChildren(entities, categoryEntity)))
                .sorted(Comparator.comparingInt(o -> (o.getSort() == null ? 0 : o.getSort())))
                .collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> ids) {
        //todo 删除逻辑判断
        baseMapper.deleteBatchIds(ids);
    }

    @Override
    public Long[] findCategoryPath(Long catId) {
        List<Long> path = new ArrayList<>();
        List<Long> categoryPath = findParentPath(catId, path);
        Collections.reverse(categoryPath);
        return path.toArray(new Long[categoryPath.size()]);
    }

    /**
     * 递归获取父分类id
     */
    private List<Long> findParentPath(Long catId, List<Long> path) {
        path.add(catId);
        CategoryEntity categoryEntity = baseMapper.selectById(catId);
        if (categoryEntity.getParentCid() != 0) {
            findParentPath(categoryEntity.getParentCid(), path);
        }
        return path;
    }

    @Override
    public String findCategoryPathName(Long catId) {
        List<String> pathName = new ArrayList<>();
        List<String> categoryPathName = findParentName(catId, pathName);
        Collections.reverse(categoryPathName);
        String result = StringUtils.join(categoryPathName.toArray(), "/");
        return result;
    }

    @Cacheable(value = {"category"}, key="#root.method.name")
    @Override
    public List<CategoryEntity> getCategoryLevel1() {
        System.out.println("缓存进入...");
        List<CategoryEntity> entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return entities;
    }

    /**
     * 本地锁解决方案
     *
     */
//    @Override
//    public Map<String, List<Catelog2Vo>> getCatelogJson(){
//        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
//        if (StringUtils.isBlank(catelogJson)){
//            synchronized (this){
//                catelogJson = redisTemplate.opsForValue().get("catelogJson");
//                if (StringUtils.isNotBlank(catelogJson)){
//                    return JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
//                }
//                System.out.println("查数据库....");
//                Map<String, List<Catelog2Vo>> catelogJsonFromDb = getCatelogJsonFromDb();
//                String s = JSONObject.toJSONString(catelogJsonFromDb);
//                redisTemplate.opsForValue().set("catelogJson",s,1, TimeUnit.DAYS);
//                return catelogJsonFromDb;
//            }
//
//        }
//        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
//        System.out.println("缓存命中....");
//        return result;
//    }

    /**
     * redis分布式锁解决方案
     */
//    @Override
//    public Map<String, List<Catelog2Vo>> getCatelogJson() {
//        /**
//         * 业务场景：
//         * 1、获取分布式锁；
//         * 2、获取到锁的线程执行查数据库的操作，查询完成更新缓存，删除锁；
//         * 3、其他线程自旋等待获取锁；
//         *
//         * 潜在的问题：
//         * 1、如果业务逻辑报错或机器断电将导致redis锁永久不能释放，则其他线程永远获取不到锁；
//         * 解决方案：给锁添加过期时间 SET NX PX（需在一个原子操作中完成创建锁和设置过期时间）
//         *
//         * 2、假设线程1获取到了分布式锁开始执行业务逻辑，在业务逻辑未处理完成的情况下锁已过期，
//         * 此时线程2获取到锁开始执行业务逻辑，正在此时线程1完成了业务逻辑操作后删除锁（因为key相同此时删除的将是线程2的锁），
//         * 锁被删除后将有新的线程获取到锁，如此反复将导致大量的请求访问到数据库；
//         * 解决方案：每次获取锁的时候给锁设置一个唯一的值，删除锁之前判断该值，相等才删除；
//         *
//         * 3、基于2，假设线程1判断锁的值成功，但在执行删除锁时，锁刚好过期了，此时又有新的线程获取到锁，
//         * 此时线程1执行删除锁删除的将是新线程的锁，这也会导致2中的问题出现；
//         * 解决方案：使用lua脚本把判断锁的值和删除锁的两个步骤合为一个原子操作；
//         */
//        String uuid = UUID.randomUUID().toString();
//        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
//        //如果缓存命中则直接返回
//        if (StringUtils.isNotBlank(catelogJson)) {
//            System.out.println("缓存命中...");
//            return JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
//            });
//        }
//        //获取redis分布式锁
//        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
//        if (lock != null && lock) {
//            try {
//                //获取到redis分布式锁，处理完业务逻辑后需要释放锁
//                catelogJson = redisTemplate.opsForValue().get("catelogJson");
//                if (StringUtils.isNotBlank(catelogJson)) {
//                    System.out.println("缓存命中...");
//                    return JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
//                    });
//                }
//                System.out.println("查数据库....");
//                Map<String, List<Catelog2Vo>> catelogJsonFromDb = getCatelogJsonFromDb();
//                String s = JSONObject.toJSONString(catelogJsonFromDb);
//                redisTemplate.opsForValue().set("catelogJson", s, 1, TimeUnit.DAYS);
//                return catelogJsonFromDb;
//            } finally {
//                //使用lua脚本保证释放锁操作的原子性（由判断当前释放的锁是否是自己的锁以及释放锁2步操作组成）
//                String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
//                Long result = redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
//                System.out.println("Long:" + result);
//            }
//        }
//        //自旋
//        try {
//            Thread.sleep(200);
//        }catch (Exception ignored){
//
//        }
//        return getCatelogJson();
//    }

    /**
     * redisson框架实现分布式锁
     */
    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
        //如果缓存命中则直接返回
        if (StringUtils.isNotBlank(catelogJson)) {
            System.out.println("缓存命中...");
            return JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
        }
        RLock lock = redisson.getLock("lock");
        lock.lock(30, TimeUnit.SECONDS);
        try {
            catelogJson = redisTemplate.opsForValue().get("catelogJson");
            //如果缓存命中则直接返回
            if (StringUtils.isNotBlank(catelogJson)) {
                System.out.println("缓存命中2222...");
                return JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
                });
            }
            Map<String, List<Catelog2Vo>> result = getCatelogJsonFromDb();
            System.out.println("查数据库...");
            String s = JSONObject.toJSONString(result);
            redisTemplate.opsForValue().set("catelogJson", s, 1, TimeUnit.DAYS);
            return result;
        } finally {
            lock.unlock();
        }
    }


    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDb() {
        long time = new Date().getTime();
        //优化：查出所有分类数据
        List<CategoryEntity> allCategories = baseMapper.selectList(null);
        //过滤出所有1级分类
        List<CategoryEntity> level1 = allCategories.stream().filter(c -> c.getParentCid() == 0).collect(Collectors.toList());
        Map<String, List<Catelog2Vo>> result = level1.stream()
                .collect(Collectors.toMap(l1 -> l1.getCatId().toString(), l1 -> {
                    //查出1级分类下的所有2级分类
                    List<CategoryEntity> level2 = filterChildCategories(allCategories, l1.getCatId());
                    List<Catelog2Vo> catelog2List = null;
                    if (level2 != null && level2.size() > 0) {
                        catelog2List = level2.stream()
                                .map(l2 -> {
                                    Catelog2Vo catelog2Vo = new Catelog2Vo(l1.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                                    //查出2级分类下的所有3级分类
                                    List<CategoryEntity> level3 = filterChildCategories(allCategories, l2.getCatId());
                                    List<Catelog2Vo.Catelog3Vo> catelog3List = null;
                                    if (level3 != null && level3.size() > 0) {
                                        //组装3级分类vo数据
                                        catelog3List = level3.stream()
                                                .map(l3 -> new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName()))
                                                .collect(Collectors.toList());
                                    }
                                    //组装2级分类数据
                                    catelog2Vo.setCatalog3List(catelog3List);
                                    return catelog2Vo;
                                }).collect(Collectors.toList());
                    }
                    if (catelog2List == null || catelog2List.size() == 0) {
                        return Collections.emptyList();
                    } else {
                        return catelog2List;
                    }
                }));
        return result;
    }

    /**
     * 过滤出指定分类下的子分类数据
     */
    private List<CategoryEntity> filterChildCategories(List<CategoryEntity> all, Long parentCid) {
        List<CategoryEntity> result = all.stream().filter(c -> c.getParentCid().equals(parentCid)).collect(Collectors.toList());
        return result;
    }

    private List<String> findParentName(Long catId, List<String> pathName) {
        CategoryEntity categoryEntity = baseMapper.selectById(catId);
        pathName.add(categoryEntity.getName());
        if (categoryEntity.getParentCid() != 0) {
            findParentName(categoryEntity.getParentCid(), pathName);
        }
        return pathName;
    }

    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        String name = category.getName();
        if (StringUtils.isNotBlank(name)) {
            categoryBrandRelationService.updateCategory(category.getCatId(), name);
        }
    }

    /**
     * 获取子分类
     */
    private List<CategoryEntity> getChildren(List<CategoryEntity> allCategory, CategoryEntity rootCategory) {
        return allCategory.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid().equals(rootCategory.getCatId()))
                .peek(categoryEntity -> categoryEntity.setChildren(getChildren(allCategory, categoryEntity)))
                .sorted(Comparator.comparingInt(o -> (o.getSort() == null ? 0 : o.getSort())))
                .collect(Collectors.toList());
    }

}